#!/usr/bin/python
#
# Smartcam Compiler
# Purpose: extract kernels and create coprocessor programs from them,
#          replacing the skeleton calls with RPC calls
# Author:  Wouter Caarls
# Date:    05-11-2004

import sys, copy, target, shutil, filecmp

from aterm import *
from cterm import *
from stratego import *

class Kernel:
  def __repr__(self):
    return "%s(%s) %s(%s) {%s}" % (self.skeleton, self.sparams, self.kernel, self.kparams, self.body)

class Callsite:
  def __repr__(self):
    return "%s @ %s: %s" % (self.call, self.line, self.kernel.kernel)

if len(sys.argv) < 2:
  print "Usage: " + sys.argv[0] + " <source>"
  sys.exit(-1)

sys.argv[1] = os.path.join(os.getcwd(), sys.argv[1])
(root, ext) = os.path.splitext(sys.argv[1])
os.chdir(os.path.dirname(sys.argv[0]))

availability = dict()
properties = ""

# Initialize targets
tclasses = target.targets()
targets = []
for t in tclasses:
  targets.append(t(root))

print "Parsing " + sys.argv[1] + ".sc"
  
# Parse input file
inputA = Parse(readall(open(sys.argv[1] + ".sc")), "parse-p -p")
if inputA == None: sys.exit(1)

input = Transform(inputA, "strategies/strip-annos")

print "Extracting typedefs"

typedefs = PPC(Transform(input, "strategies/extract-typedefs"))

print "Extracting skeletons"

# Extract skeleton/kernel combinations from input file
kernels = InterpretATerm(Transform(input, "strategies/extract-kernels")).children

# Fill a dictionary with them
kdict = dict()
for k in kernels:
  nk = Kernel()
  nk.skeleton = k.sdef().id().name()
  nk.kernel = k.kdef().id().name()
  nk.sparams = k.sdef().paramlist().paramdecls().children
  nk.kparams = k.kdef().paramlist().paramdecls().children
  nk.body = k.body()
  kdict[nk.kernel] = nk

print "Extracting callsites"

# Extract all function calls from input file
fcalls = InterpretATerm(Transform(input, "strategies/extract-callsites")).children
fcallsA = InterpretATerm(Transform(inputA, "strategies/extract-callsites")).children

# Place kernel calls in a list
kcalls = list()
for (c, cA) in zip(fcalls, fcallsA):
  if c.id().name() in kdict:
    # Kernel call. Rewrite K(...) into S(K, ...)
    k = kdict[c.id().name()]
    
    nc = Callsite()
    nc.kernel = k
    ps = copy.deepcopy(c.params())
    ps.children.insert(0, c.id())
    nc.params = ps.children
    nc.call = copy.deepcopy(c)
    nc.line = cA.annotations[0].children[1].data
    kcalls.append(nc)
  else:
    # Find out if this is a skeleton call
    for i in range(len(c.params().children)):
      p = c.params().children[i]

      if isinstance(p, Id):
        if p.name() in kdict:
          # Kernel call
          k = kdict[p.name()]

          if k.skeleton != c.id().name():
            raise "Kernel %s called with wrong skeleton (%s instead of %s)" % (k.kernel, c.id().name(), k.skeleton)

          nc = Callsite()
          nc.kernel = k
          nc.call = copy.deepcopy(c)
          nc.line = cA.annotations[0].children[1].data
          nc.params = copy.deepcopy(c.params().children)

          kcalls.append(nc)
          break

print "Instantiating skeleton calls"

# Process each skeleton/kernel combination in turn
rpc = 16
for c in kcalls:
  print "  Instantiating %s(%s) at line %s" % (c.kernel.skeleton, c.kernel.kernel, c.line)
  print "    Generating signature"

  # First, add defaults
  newparams = []
  count = 0
  for p in c.params:
    if isinstance(p, Id) and p.name() == c.kernel.kernel:
      if count < len(c.kernel.sparams):
        # Add skeleton parameter defaults
        for i in range(count, len(c.kernel.sparams)):
          if c.kernel.sparams[i].init() != ATerm("None"):
            newparams.append(c.kernel.sparams[i].init())
          else:
            raise "Too few skeleton parameters in call to %s(%s) at line %s" % (c.kernel.skeleton, c.kernel.kernel, c.line)

      newparams.append(p)
      count = 0
      continue

    newparams.append(p)
    count = count + 1

  if count < len(c.kernel.kparams):
    # Add kernel parameter defaults
    for i in range(count, len(c.kernel.kparams)):
      if c.kernel.kparams[i].init() != ATerm("None"):
        newparams.append(c.kernel.kparams[i].init())
      else:
        raise "Too few kernel parameters in call to %s(%s) at line %s" % (c.kernel.skeleton, c.kernel.kernel, c.line)

  c.params = newparams

  # Then, construct kernel initialization based on completed parameter set
  skelin = """
#include "stdsc.h"

%s

ilp_arg_t args[%d];
instance_t instance[1];
int ii;

instance[0].arguments = %d;

for (ii=0; ii < instance[0].arguments; ii++)
{
  instance[0].argcode[ii].connector.instance = -1;
  instance[0].argcode[ii].connector.argument = ii;
  instance[0].argcode[ii].argid = `args[$ii]`;
}

{
  #include "data/%s-%s-%s.sig"

  build(instance[0].argcode, &instance[0].kernel);
}

""" % (typedefs, len(c.params)-1, len(c.params)-1, c.kernel.skeleton, c.kernel.kernel, c.line)

  signature = """
void build(argcode_t *argcode, code *body)
{
"""
  
  pcount = 0
  count = 0
  isKernel = False
  fmt = ""
  arguments = []
  
  for p in c.params:
    # Construct format
    if isinstance(p, Id) and p.name() == c.kernel.kernel:
      isKernel = True
      count = 0
      continue

    # Get formal parameters
    # TODO: scalar outputs
    if isKernel:
      fp = c.kernel.kparams[count]
    else:
      fp = c.kernel.sparams[count]

    # Parameter name
    signature += "  argcode[%d].id = `%s`;\n" % (pcount, fp.iddecl().id().name())
    a = target.AvailabilityArgument(pcount, fp.iddecl().id().name())

    # Argument type, format, and constant propagation
    storage = fp.typespec().storageclass().children
    spec = fp.typespec().typespec()
    
    if ATerm("Stream") in storage:
      a.type = "stream"
      if ATerm("In") in storage:
        fmt += "i"
        a.intent = "in"
        signature += "  argcode[%d].argtype = ARG_STREAM_IN;\n" % (pcount)
      elif ATerm("Out") in storage:
        fmt += "I"
        a.intent = "out"
        signature += "  argcode[%d].argtype = ARG_STREAM_OUT;\n" % (pcount)
      else:
        raise "Stream argument of %s(%s) defined without direction" % (c.kernel.skeleton, c.kernel.kernel)
    elif ATerm("Lookup") in storage:
      a.type = "stream"
      a.intent = "in"
      fmt += "i"
      signature += "  argcode[%d].argtype = ARG_LOOKUP;\n" % (pcount)
    else:
      if ATerm("In") in storage:
        signature += "  argcode[%d].argtype  = ARG_SCALAR_IN;\n" % (pcount)
        a.intent = "in"
      elif ATerm("Out") in storage:
        signature += "  argcode[%d].argtype  = ARG_SCALAR_OUT;\n" % (pcount)
        a.intent = "out"
      
      if spec.name()[-4:-1] == "int":
        a.type = "int"
        if ATerm("In") in storage:
          fmt += "d"
        elif ATerm("Out") in storage:
          fmt += "D"
         
        if isinstance(p, FunCall) and p.id().name() == "scint" and len(p.params().children) == 1 and (isinstance(p.params().children[0], IntConst) or isinstance(p.params().children[0], FloatConst)):
          signature += "  (@argcode[%d].argid).data.d = %d;\n" % (pcount, int(p.params().children[0].value()))
      elif spec.name()[-7:-1] == "double":
        a.type = "double"
        if ATerm("In") in storage:
          fmt += "f"
        elif ATerm("Out") in storage:
          fmt += "F"
          
        if isinstance(p, FunCall) and p.id().name() == "scdbl" and len(p.params().children) == 1 and (isinstance(p.params().children[0], IntConst) or isinstance(p.params().children[0], FloatConst)):
          signature += "  (@argcode[%d].argid).data.f = %f;\n" % (pcount, float(p.params().children[0].value()))
      else:
        a.type = "pointer"
        #raise "Kernel argument %d has unknown type %s" % (pcount+1, fp.typespec().name())
        
    # Datatype
      
    signature += "  argcode[%d].datatype = `%s`;\n" % (pcount, spec.name())
    a.datatype = spec.name()
    
    # Array declarations
    arraydecls = fp.iddecl().paramlist().children
    ac = 0
    for d in arraydecls:
      if d.data == "EmptyArrayDecl":
        signature += "  argcode[%d].extents[%d].start = 0;\n" % (pcount, ac)
        signature += "  argcode[%d].extents[%d].end = 0;\n" % (pcount, ac)
        signature += "  argcode[%d].extents[%d].size = 0;\n" % (pcount, ac)
      else:      
        if d.end() < d.start():
          raise "Array declaration has lower end than start"
        signature += "  argcode[%d].extents[%d].start = %d;\n" % (pcount, ac, d.start())
        signature += "  argcode[%d].extents[%d].end = %d;\n" % (pcount, ac, d.end())
        signature += "  argcode[%d].extents[%d].size = %d;\n" % (pcount, ac, d.end()-d.start()+1)
      ac = ac + 1

    for j in range(ac, 2):
      signature += "  argcode[%d].extents[%d].start = argcode[%d].extents[%d].end = 0;\n" % (pcount, j, pcount, j)
      signature += "  argcode[%d].extents[%d].size = 1;\n" % (pcount, j)

    count = count + 1
    pcount = pcount + 1
    
    arguments.append(a)
          
  signature += """

  *body = `{
%s
  }`;
}
""" % (PPC(c.kernel.body))

  print >>open("data/" + c.kernel.skeleton + "-" + c.kernel.kernel + "-" + c.line + ".sig", "w"), signature

  availability[rpc] = target.Availability(rpc, c.kernel.skeleton + "-" + c.kernel.kernel + "-" + c.line, c.kernel.skeleton)
  availability[rpc].arguments = arguments

  # Call skeleton instantiators
  for t in targets:
    print "    " + str(t) + " target"

    propinit = """
#include "prop.h"

unsigned char operation[] = "%s-%s-%s";
unsigned char target[] = "%s";

propInit("data/%s-%s-%s-%s.xml");
""" % (c.kernel.skeleton, c.kernel.kernel, c.line, str(t),
       c.kernel.skeleton, c.kernel.kernel, c.line, str(t))

    propfini = """
propFini();
""";

    meta = ""
    try:
      f = open(str(t) + "/" + c.kernel.skeleton + ".meta")
      meta = f.readline()
    except:
      pass
      
    if meta != "":
      skelin2 = """
#include "%s/%s.s"

%s
%s

instance[0].skeleton = `%s`;
instance[0].body = `{
#include "%s/%s.s"
}`;

%s(instance, 1);
    
%s
""" % (str(t), meta, skelin, propinit, c.kernel.skeleton, str(t), c.kernel.skeleton, meta, propfini)    
    else:
      skelin2 = """
#include "%s/%s.s"

%s
%s

%s(instance[0].argcode, instance[0].arguments, instance[0].kernel);
    
%s
""" % (str(t), c.kernel.skeleton, skelin, propinit, c.kernel.skeleton, propfini)    
      
    g = open("data/" + c.kernel.skeleton + "-" + c.kernel.kernel + "-" + c.line + "-" + str(t) + ".p", "w")
    print >>g, skelin2
    g.close()

    print "      Calling C preprocessor"
    cppout = FreeTransform(FreeTransform(skelin2, "cpp -Iinclude -I."), "grep -v \"^# \"")
    #cppout = FreeTransform(skelin2, "cpp -Iinclude -I.")

    if not cppout:
      print "Couldn't run preprocessor, skipping target"
      continue

    cppoutname = "data/" + c.kernel.skeleton + "-" + c.kernel.kernel + "-" + c.line + "-" + str(t) + ".i"
    pepcioutname = "data/" + c.kernel.skeleton + "-" + c.kernel.kernel + "-" + c.line + t.ext()

    f = open(cppoutname + "~", "w")
    print >>f, cppout
    f.close()    
    
    try:
      if filecmp.cmp(cppoutname, cppoutname + "~", False) == False:
        raise ValueError
      if os.path.getmtime(cppoutname) > os.path.getmtime(pepcioutname):
        raise ValueError
      print "      Using cached PEPCI result"
    except:
      shutil.copyfile(cppoutname + "~", cppoutname)
      print "      Running PEPCI"
      call = "$SC_TOP/src/pepci/src/pepci -i " + cppoutname + " -o " + pepcioutname + " -s $SC_TOP/src/pepci/strategies -d args -d scu_buf_t -d scu_bufptrid_t -d extents_t -d ilp_arg_t -d argcode_t -d log_default_streams_t -d ilpl -d op -d ilp_op_t -d lmem -d reg -d larg0 -d larg1 -d larg2 -d larg3 -d rarg0 -d rarg1 -d rarg2 -d rarg3 -a -c " + c.kernel.skeleton + "-" + str(t) + " -lm -l../libprop/libprop.so -f"
      print call
        
      if os.system(call):
        print "Couldn't run PEPCI, skipping target"
        continue

    availability[rpc].processors.append(target.AvailabilityProc(str(t), t.type()))
    f = open("data/%s-%s-%s-%s.xml" % (c.kernel.skeleton, c.kernel.kernel, c.line, str(t)))
    properties += readall(f)
    f.close()
 
    f = open("data/" + c.kernel.skeleton+ "-" + c.kernel.kernel + "-" + c.line + t.ext(), "r")
    skelout = readall(f)
    f.close()

    t.addkernel(rpc, c.kernel.skeleton, c.kernel.kernel, skelout)
    
  print "  Replacing callsites"
  # Construct RPC call parameters
  rpcparams = [ATerm("StringLit", [ATerm("[]", [ATerm("\"\\\"" + fmt + "\\\"\"")])]), \
               ATerm("IntConst", [ATerm("\"" + str(rpc) + "\"")])]
  rpcparams += c.params
  rpcparams.remove(Id(c.kernel.kernel))
  
  repl = FunCall(Id("scOp%s" %(len(rpcparams)-2)), ATerm("[]", rpcparams))

  # Replace Skeleton(Kernel, ...) calls with scOp(format, rpc, ...)
  input = Transform(ATerm("()", [c.call, repl, input]), "strategies/replace-once")

  if input == None: sys.exit(1)

  rpc = rpc + 1

print "Removing kernel definitions"
input = Parse(PPC(Transform(input, "strategies/remove-kernels")), "parse-p -p")

if "--instrument" in sys.argv:
  print "Instrumenting"
  input = Transform(input, "strategies/instrument")

# Output coprocessor program and new main program (without kernels)
print >>open(os.path.join(os.path.dirname(sys.argv[1]), "main.cpp"), "w"), PPC(input)

print "Outputting sources"
for t in targets:
  print "  " + str(t) + " target"
  t.write(typedefs)

print "Outputting availability"

# Open availability file
av = open(sys.argv[1] + "-availability.xml", "w")
avtxt = open(sys.argv[1] + "-availability.txt", "w")
print >>av, "<document>"
print >>av, properties
print >>av, "<operations>"

for a in availability.values():
  print >>av, str(a)

  for p in a.processors:
    print >>avtxt, a.rpc, p.type

print >>av, "</operations>"
print >>av, "</document>"

av.close()
avtxt.close()
